package com.example.serviceCourse.schoolGet;

import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.util.HexUtil;
import com.example.serviceCourse.StuInfoGet;
import com.example.serviceCourse.ZSON;
import ohos.data.DatabaseHelper;
import ohos.data.preferences.Preferences;
import ohos.utils.zson.ZSONArray;
import ohos.utils.zson.ZSONObject;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.safety.Whitelist;
import org.jsoup.select.Elements;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static com.example.serviceCourse.schoolGet.s.ShuWei.getStr;
import static com.example.serviceCourse.schoolGet.s.ShuWei.getValue;


public abstract class schoolBase {
    //记录教务能力，若可以执行为true，否则为false
    public boolean courseGet = true; //必选
    public boolean scoreGet = false; //可选
    public boolean gpaGet = false; //可选
    public boolean examGet = false; //可选
    public boolean loadNextTerm = false; //可选，支持提前导入下学期
    public boolean canLink = true; //支持绑定教务课表
    public int verifyCodeType = 0; //0--普通文字验证码, 1--扫码
    public String codeSuffix = ".jpg";
    public boolean needVerifyCode = false; // 是否需要验证码（目前仅支持图片验证码），需要则无法支持卡片功能，仅能用于页面内教务使用
    public String[] courseTime = new String[]{}; //课程时间串，第一个为课程1节持续时间，后续为第n节课的起始时间
    public boolean canLoadResult = false;

    public static TimedCache<String, String> cache = new TimedCache<>(5*60000);
    protected static String _startDate = "2022-01-03";
    protected static boolean login;
    protected static Map<String, String> cookies;
    protected static Map<String, String> vpnCookies;
    protected static final HashMap<String, String> defaultHeader = new HashMap<String, String>() {{
        put("Accept", "*/*");
        put("Connection", "keep-alive");
        put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 HBPC/11.0.6.301");
    }};

    protected static List<String> weekdayInChinese = Arrays.asList("周一","周二","周三","周四","周五","周六","周日");

    //登录函数，初始化时完成登录，返回JSON字符串或者JSONObject,如果不需要验证码，请忽视verifyCode这个参数，在需要验证码时他会带上用户输入的验证码值
    //内容包括---
    //name:String //学生姓名
    public abstract String Login(String id,String pas,String verifyCode);

    //获取最新学期的课表，返回JSON字符串或者JSONObject，错误返回null，请检测登陆过期，并在过期时重新登陆，再获取信息
    //内容格式示例：
    //{courses:[[{idx:1,name:xxx,last:"02:00",teacher:xxx,place:xxx,week:["1-16"]}],[],[],[],[],[],[]],startDate:"2021-01-04"}
    //---courses:Array<Array<Object>> 记录课程信息，内容如下：
    //idx:Number 课程索引，按照课程次序从大到小，1为第一节课，依次后推
    //name:String 课程名称
    //last:Number 课程持续时间
    //teacher:String, place:String 可选
    //week:Array<String> 可有格式 "1-16","2"
    //返回的数组必须长度为7，记录顺序为周一，周二，...，周日
    //---startDate:String 课程表的起始日期，需为周一，若无法获得请置本年第一个周一
    //---warn:String 可选，提示信息，当下载课表时将弹出
    public abstract String getCourses(boolean nextTerm);

    public String getCourses(){
        return getCourses(false);
    }

    //获取成绩，返回JSON字符串或者JSONObject，错误返回null，请检测登陆过期，并在过期时重新登陆，再获取信息
    //内容格式示例：
    //[{name:xxx,value:80}]
    //name:String 课程名称
    //value:Number/String 课程分数，可显示A+等
    public String getScores(){return null;}

    //获取学分绩，返回JSON字符串或者JSONObject，错误返回null，请检测登陆过期，并在过期时重新登陆，再获取信息
    //内容格式示例：
    //{gpa: 3.9,all:100,at:30}
    //gpa:Number 学分绩
    //all:Number 总人数，可选，无可填-1
    //at:Number 你的排名
    public String getGPA(){return null;}

    //若需要验证码，请在此函数获取验证码图片并返回
    public byte[] getVerifyCode(){return null;}

    //获取近期的考试安排，返回JSON字符串或JSONObject，错误返回null，请检测登录过期，并在过期时重新登陆，再获取信息
    //内容格式示例：
    //[{name:xxx,startDate:2021-09-20,place:xxx,time:14:40~16:40}]
    //name:String 课程名称
    //startDate:String 考试日期
    //place:String 考试地点
    //time:String 考试时间，需用 ~ 分割两段时间，呈xx:xx~xx:xx
    public String getExams(){return null;}

    //导入识别结果，大部分教务不需要，仅青果教务使用
    public String getLoadResult(String filePath){
        return null;
    }

    public boolean checkLogin(){
        return login;
    }

    //退出程序时调用
    public void LoginOut(){
        System.out.println("Login out");
        _LoginOut();
    }

    protected void _LoginOut(){

    }

    @Override
    protected void finalize() throws Throwable {
        LoginOut();
    }

    protected void getLoadTime(){
        String[] def = new String[]{"08:30", "09:25","10:20","11:15","14:00","14:55","15:50","16:45","18:30","19:25","20:20","21:15"};
        try {
            DatabaseHelper helper = new DatabaseHelper(StuInfoGet.context.getApplicationContext());
            Preferences store = helper.getPreferences("Storage");
            ZSONObject loadTime = ZSONObject.stringToZSON(store.getString("loadTime", ""));
            if (loadTime == null) {
                loadTime = new ZSONObject();
                loadTime.put("last", "00:45");
                loadTime.put("times", ZSON.createArray().pushAll(Arrays.asList(def)).getArray());
            }
            ZSONArray t = loadTime.getZSONArray("times");
            t.add(0, loadTime.getString("last"));
            this.courseTime = t.toArray(new String[]{});
        }catch (Exception e){
            System.out.println(e.toString());
        }
    }

    protected String getUrl() {
        DatabaseHelper helper = new DatabaseHelper(StuInfoGet.context.getApplicationContext());
        Preferences store = helper.getPreferences("Storage");
        String url = store.getString("universalUrl", "");
        if(url.endsWith("/")) url = url.substring(0, url.length() - 1);
        return url;
    }

    protected String[] getVpnData() {
        DatabaseHelper helper = new DatabaseHelper(StuInfoGet.context.getApplicationContext());
        Preferences store = helper.getPreferences("Storage");
        String vpnString = store.getString("vpnData", "{\"id\":\"\", \"pas\": \"\"}");
        ZSONObject vpnData = ZSONObject.stringToZSON(vpnString);
        String[] data = new String[2];
        data[0] = vpnData.getString("id");
        data[1] = vpnData.getString("pas");
        return data;
    }

    protected String fill(String s) {
        if (s.length() == 1) return "0" + s;
        else return s;
    }

    protected String fill(int n) {
        if (n < 10) return "0" + n;
        else return String.valueOf(n);
    }

    protected Connection.Response authLogin(String Url, String id, String pas) {
        return authLogin(Url, id, pas, true);
    }

    protected Connection.Response authLogin(String Url, String id, String pas, boolean encrypt) {
        try {
            Connection.Response response = Jsoup.connect(Url).headers(defaultHeader).cookies(cookies).execute();
            cookies.putAll(response.cookies());
            System.out.println("login 1");
            Document d = Jsoup.parse(response.body());
            String lt = d.select("input[name=lt]").first().attr("value");
            String execution = d.select("input[name=execution]").first().attr("value");
            String eventId = d.select("input[name=_eventId]").first().attr("value");
            String rmShown = d.select("input[name=rmShown]").first().attr("value");
            String pwd;
            if(encrypt) {
                String salt;
                try {
                    salt = d.select("input[id=pwdDefaultEncryptSalt]").first().attr("value");
                } catch (Exception ignored) {
                    salt = d.getElementById("passwordEncrypt").val();
                }
                if (salt.isEmpty()) {
                    String code = d.getElementsByTag("script").html();
                    int idx = code.indexOf("pwdDefaultEncryptSalt");
                    idx = code.indexOf("\"", idx) + 1;
                    salt = code.substring(idx, code.indexOf("\"", idx));
                }

                String data = _rds(64) + pas;
                String iv0 = _rds(16);
                byte[] keyByte = salt.getBytes(StandardCharsets.UTF_8);
                byte[] iv = iv0.getBytes(StandardCharsets.UTF_8);
                SecretKeySpec skeySpec = new SecretKeySpec(keyByte, "AES");
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
                byte[] result = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
                pwd = Base64.getEncoder().encodeToString(result);
            }else pwd = pas;

            Connection.Response loginResponse = Jsoup.connect(response.url().toString()).headers(defaultHeader).cookies(cookies)
                    .timeout(120000).referrer(Url)
                    .ignoreContentType(true)
                    .data("username",id)
                    .data("password", pwd)
                    .data("lt",lt)
                    .data("dllt","userNamePasswordLogin").data("execution",execution)
                    .data("_eventId",eventId).data("rmShown",rmShown)
                    .method(Connection.Method.POST)
                    .ignoreHttpErrors(true)
                    .execute();
            System.out.println("login 2");
            return loginResponse;
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return null;
    }

    protected String getAESEncrypt(Document d, String _data){
        try {
            String salt;
            try {
                salt = d.select("input[id=pwdDefaultEncryptSalt]").first().attr("value");
            } catch (Exception ignored) {
                salt = d.getElementById("passwordEncrypt").val();
            }
            if (salt.isEmpty()) {
                String code = d.getElementsByTag("script").html();
                int idx = code.indexOf("pwdDefaultEncryptSalt");
                idx = code.indexOf("\"", idx) + 1;
                salt = code.substring(idx, code.indexOf("\"", idx));
            }

            String data = _rds(64) + _data;
            String iv0 = _rds(16);
            byte[] keyByte = salt.getBytes(StandardCharsets.UTF_8);
            byte[] iv = iv0.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec skeySpec = new SecretKeySpec(keyByte, "AES");
            // "算法/模式/补码方式"
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
            byte[] result = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            String encrypt = Base64.getEncoder().encodeToString(result);
            return encrypt;
        }catch (Exception e){
            System.out.println(e.toString());
            return null;
        }
    }

    protected Connection.Response authLogin2(String Url, String id, String pas){
        try{
            cookies = new HashMap<>();
            Connection.Response index = Jsoup.connect(Url).headers(defaultHeader)
                    .execute();
            cookies.putAll(index.cookies());
            Connection.Response Login = Jsoup.connect(Url).headers(defaultHeader).cookies(cookies)
                    .method(Connection.Method.POST).data("username", id)
                    .data("password", pas).data("currentMenu", "1").data("failN", "-1")
                    .data("execution", "e1s1").data("_eventId", "submit")
                    .data("geolocation", "").data("submit", "稍等片刻……")
                    .execute();
            return Login;
        }catch (Exception e){
            System.out.println(e.toString());
        }
        return null;
    }

    protected Connection.Response autoLogin3(String Url, String id, String pas){
        try {
            Connection.Response pageRes = Jsoup.connect(Url).headers(defaultHeader).ignoreContentType(true).timeout(10000)
                    .cookies(cookies).execute();
            cookies.putAll(pageRes.cookies());
            Document d = Jsoup.parse(pageRes.body());
            String salt = d.getElementById("pwdEncryptSalt").val();
            String execution = d.getElementById("execution").val();
            String dllt = d.getElementById("dllt") == null ? "" : d.getElementById("dllt").val();
            String _eventId = d.getElementById("_eventId").val();

            String data = _rds(64) + pas;
            String iv0 = _rds(16);
            byte[] keyByte = salt.getBytes(StandardCharsets.UTF_8);
            byte[] iv = iv0.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec skeySpec = new SecretKeySpec(keyByte, "AES");
            // "算法/模式/补码方式"
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
            byte[] result = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            String encrypt = Base64.getEncoder().encodeToString(result);

            return Jsoup.connect(pageRes.url().toString()).headers(defaultHeader).cookies(cookies)
                    .referrer(pageRes.url().toString())
                    .ignoreContentType(true)
                    .data("username", id)
                    .data("password", encrypt)
                    .data("captcha", "").data("lt", "")
                    .data("_eventId", _eventId)
                    .data("execution", execution)
                    .data("cllt", "userNameLogin").data("dllt", dllt).method(Connection.Method.POST).execute();
        }catch (Exception e){
            System.out.println(e.toString());
        }
        return null;
    }

    protected Connection.Response authLogin4(String Url, String id, String pas, String saltUrl){
        try {
            cookies = new HashMap<>();
            Connection.Response response = Jsoup.connect(Url).headers(defaultHeader).execute();
            cookies.putAll(response.cookies());
            System.out.println("login 1");
            Document d = Jsoup.parse(response.body());
            String lt = d.select("input[name=lt]").first().attr("value");
            String execution = d.select("input[name=execution]").first().attr("value");
            String eventId = d.select("input[name=_eventId]").first().attr("value");
            String rmShown = d.select("input[name=rmShown]").first().attr("value");
            String script = d.getElementsByTag("script").html();
            int idx = script.indexOf("pwdDefaultEncryptSalt");
            String salt = getStr(getValue(idx, script));

            String data = _rds(64) + pas;
            String iv0 = _rds(16);
            byte[] keyByte = salt.getBytes(StandardCharsets.UTF_8);
            byte[] iv = iv0.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec skeySpec = new SecretKeySpec(keyByte, "AES");
            // "算法/模式/补码方式"
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
            byte[] result = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            String encrypt = Base64.getEncoder().encodeToString(result);

            Connection.Response loginResponse = Jsoup.connect(Url).headers(defaultHeader).cookies(cookies)
                    .timeout(10000)
                    .ignoreContentType(true)
                    .data("username",id)
                    .data("password",encrypt)
                    .data("lt",lt)
                    .data("dllt","userNamePasswordLogin").data("execution",execution)
                    .data("_eventId",eventId).data("rmShown",rmShown)
                    .method(Connection.Method.POST)
                    .ignoreHttpErrors(true)
                    .execute();
            System.out.println("login 2");
            return loginResponse;
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return null;
    }

    protected static ZSONArray stringToWeek(String str, int ofx) {
        ZSONArray week = new ZSONArray();
        int len = str.length();
        for (int i = 0; i < len; i++) {
            if (str.charAt(i) == '1') week.add(String.valueOf(i + ofx));
        }
        return week;
    }

    protected static ZSONObject createCourse(String name, String teacher, String place, ZSONArray week, int idx, int last) {
        return ZSON.createObject().push("name", name).push("teacher", teacher)
                .push("place", place).push("week", week).push("idx", idx).push("last", last)
                .getObject();
    }

    protected static ZSONObject createScore(String name, String value) {
        return ZSON.createObject().push("name", name).push("value", value).getObject();
    }

    protected static ZSONArray createCourses() {
        ZSONArray courses = new ZSONArray();
        for (int i = 0; i < 7; i++) courses.add(new ZSONArray());
        return courses;
    }

    protected static ZSONObject createExam(String name, String place, String date, String time) {
        return ZSON.createObject().push("name", name).push("place", place).push("startDate", date)
                .push("time", time).getObject();
    }

    public static Element[][] dealTable(Element table) {
        return dealTable(table, true);
    }

    protected static Element[][] dealTable(Element table, boolean copy) {
        String _table;
        if(!table.tagName().equals("tbody") && table.getElementsByTag("tbody").size() != 0){
            _table = "<table>" + table.getElementsByTag("tbody").get(0).children().html() + "</table>";
        }else if(table.tagName().equals("tbody")){
            String children = table.html();
            _table = "<table>" + children + "</table>";
        }
        else _table = table.html();
        table = Jsoup.parse(Jsoup.clean(_table, Whitelist.relaxed().addTags("hr"))).body().child(0).child(0);
        Elements rows = table.children();
        ArrayList<ArrayList<Element>> tmp = new ArrayList<>();
        int maxcol = 0;
        for (int i = 0; i < rows.size(); i++) {
            tmp.add(new ArrayList<>());
        }
        for (Element row : rows) {
            Elements cols = row.children();
            if (maxcol < cols.size()) maxcol = cols.size();
        }
        boolean[][] mask = new boolean[rows.size()][maxcol];
        int[] lines = new int[rows.size()];
        for(int j=0;j<maxcol;j++) {
            for (int i = 0; i < rows.size(); i++) {
                if(mask[i][j]) continue;
                if(j - lines[i] >= rows.get(i).childrenSize()) continue;
                Element item = rows.get(i).child(j - lines[i]);
                int span = item.hasAttr("rowspan") ? Integer.parseInt(item.attr("rowspan")) : 1;
                for (int k = 0; k < span; k++){
                    tmp.get(i + k).add((copy || k == 0) ? item : null);
                    mask[i+k][j] = true;
                    lines[i+k] ++;
                }
                lines[i]--;
            }
        }
        Element[][] res = new Element[rows.size()][maxcol];
        for (int i = 0; i < rows.size(); i++) res[i] = tmp.get(i).toArray(new Element[0]);
        return res;
    }

    protected String getCacheName() {
        return ZSON.createObject().push("name", cache.get("name")).toString();
    }

    protected static String[] dealNodes(List<Node> nodes){
        ArrayList<String> res = new ArrayList<>();
        for(Node n : nodes){
            if(n.getClass().equals(Element.class)){
                res.add(((Element) n).text());
            }else if(n.getClass().equals(TextNode.class)){
                res.add(((TextNode) n).text());
            }else res.add("");
        }
        return res.toArray(new String[0]);
    }

    static String chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
    public static String _rds(int len) {
        StringBuilder retStr = new StringBuilder();
        for (int i = 0; i < len; i++) {
            retStr.append(chars.charAt((int)(Math.random() * chars.length())));
        }
        return retStr.toString();
    }

    public static ZSONArray getWeek(String[] weeks) {
        return getWeek(weeks, null);
    }

    public static ZSONArray getWeek(String[] weeks, Integer tp) {
        ZSONArray week = new ZSONArray();
        for (String v : weeks) {
            int type = 2;
            if (tp != null) type = tp;
            else {
                if (v.contains("单")) type = 1;
                if (v.contains("双")) type = 0;
            }
            v = v.replaceAll("[^\\d-]", "");
            if(v.equals("")) continue;
            int s, e;
            if (v.contains("-")) {
                String[] t = v.split("-");
                s = Integer.parseInt(t[0]);
                e = Integer.parseInt(t[1]);
            } else {
                s = e = Integer.parseInt(v);
            }
            if (type == 2) week.add(v);
            else {
                for (int i = s; i <= e; i++) {
                    if (i % 2 == type) week.add(String.valueOf(i));
                }
            }
        }
        return week;
    }

    public static int checkWeekType(String str){
        int type = 2;
        if(str != null){
            if(str.contains("单")) type = 1;
            if(str.contains("双")) type = 0;
        }
        return type;
    }

    public Element getTbody(Element table){
        Elements tboyds = table.getElementsByTag("tbody");
        if(tboyds.size() != 0) return tboyds.first();
        else return table;
    }
    protected boolean webVPNLogin(String url, String id, String pas){
        return webVPNLogin(url, id, pas, false);
    }

    private String textRightAppend(String text, int segmentByteSize) {

        if (text.length() % segmentByteSize == 0) {
            return text;
        }

        int appendLength = segmentByteSize - text.length() % segmentByteSize;
        int i = 0;
        StringBuilder textBuilder = new StringBuilder(text);
        while (i++ < appendLength) {
            textBuilder.append("0");
        }
        text = textBuilder.toString();
        return text;
    }

    protected String webVpnEncKey = "wrdvpnisawesome!";
    protected String webVpnEncIv = "wrdvpnisawesome!";
    protected boolean webVPNLogin(String url, String id, String pas, boolean encrypt){

        try {
            if(id.isEmpty() || pas.isEmpty()) return false;
            Connection.Response lR = Jsoup.connect(url + "/login").headers(defaultHeader).execute();
            vpnCookies.putAll(lR.cookies());
            Document p = Jsoup.parse(lR.body());

            String enc = "";
            if(encrypt){
                int textLength = pas.length();
                pas = textRightAppend(pas, 16);
                byte[] keyBytes = webVpnEncKey.getBytes(StandardCharsets.UTF_8);
                byte[] ivBytes = webVpnEncIv.getBytes(StandardCharsets.UTF_8);
                byte[] textBytes = pas.getBytes(StandardCharsets.UTF_8);
                SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
                Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(ivBytes));
                byte[] result = cipher.doFinal(textBytes);
                enc = HexUtil.encodeHexStr(ivBytes) + HexUtil.encodeHexStr(result).substring(0, textLength * 2);
            }else enc = pas;

            String captcha_id = p.getElementsByAttributeValue("name", "captcha_id").val();
            Connection.Response Login = Jsoup.connect(url + "/do-login").headers(defaultHeader)
                    .cookies(vpnCookies).method(Connection.Method.POST).ignoreContentType(true).data("auth_type", "local")
                    .data("username", id).data("sms_code", "").data("password", enc)
                    .data("captcha", "").data("needCaptcha", "false")
                    .data("captcha_id", captcha_id).execute();
            System.out.println(Login.body());
            if(Login.url().toString().contains("/do-login") && Login.body().contains("true")){
                vpnCookies.putAll(Login.cookies());
                System.out.println("Vpn login success");
                return true;
            }
        }catch (Exception e){
            System.out.println(e.toString());
        }
        vpnCookies = null;
        return false;
    }

    protected boolean webVpnLoginTwice = true;

    protected boolean webVPNLogin2(String url, String id, String pas, String finalCompare) {
        try {
            if(finalCompare == null || finalCompare.isEmpty()) finalCompare = url;
            Connection.Response lR = Jsoup.connect(url + "/users/sign_in").headers(defaultHeader).execute();
            cookies.putAll(lR.cookies());
            Document p = Jsoup.parse(lR.body());
            String authenticity_token = p.getElementsByAttributeValue("name", "authenticity_token").val();

            Connection.Response Login = Jsoup.connect(url + "/users/sign_in").headers(defaultHeader)
                    .cookies(cookies).method(Connection.Method.POST).ignoreContentType(true).data("utf8", "✓")
                    .data("authenticity_token", authenticity_token).data("user[check_path]", "login")
                    .data("user[otp_with_capcha]", "false").data("user[double_factor]","false")
                    .data("user[login]", id).data("user[password]", pas).data("user[dymatice_code]", "unknown")
                    .data("commit", "登录 Login").execute();

            System.out.println(Login.url().toString());
            cookies.putAll(Login.cookies());
            if(webVpnLoginTwice) {
                Login = Jsoup.connect(url + "/users/sign_in").headers(defaultHeader)
                        .cookies(cookies).method(Connection.Method.POST).ignoreContentType(true).data("utf8", "✓")
                        .data("authenticity_token", authenticity_token).data("user[check_path]", "login")
                        .data("user[otp_with_capcha]", "false").data("user[double_factor]", "false")
                        .data("user[login]", id).data("user[password]", pas).data("user[dymatice_code]", "unknown")
                        .data("commit", "登录 Login").execute();
            }

            System.out.println(Login.url().toString());
            if(Login.url().toString().equals(finalCompare)){
                System.out.println("Login vpn success");
                cookies.putAll(Login.cookies());
                return true;
            }else if(Login.body().contains("已在其他位置登录")){
                return webVPNLogin2(url, id, pas, finalCompare);
            }
        }catch (Exception e){
            System.out.println(e.toString());
        }
        return false;
    }

    protected static String[] readParam(String str){
        int s = 0, n = 0;
        ArrayList<String> res = new ArrayList<>();
        int len = str.length();
        boolean inStr = false;
        while(n < len){
            char ch = str.charAt(n);
            switch (ch){
                case '"': inStr = !inStr;break;
                case ',':{
                    if(inStr) break;
                    res.add(str.substring(s, n).trim().replace("\"", ""));
                    s = n + 1;
                    break;
                }
            }
            n ++;
        }
        res.add(str.substring(s, n).trim().replace("\"", ""));
        return res.toArray(new String[0]);
    }

    protected static String getUrlParam(String url, String key){
        int idx = url.indexOf(key) + key.length();
        if(idx == -1) return null;
        idx = url.indexOf("=", idx) + 1;
        int end = url.indexOf("&", idx);
        if(end == -1) end = url.length();
        return url.substring(idx, end);
    }

    protected Connection.Response tryConnect(Connection conn, int times){
        for(int i=0;i<times;i++){
            try{
                return conn.execute();
            }catch (Exception e){
                System.out.println("retry");
            }
        }
        return null;
    }

    private String qr_uuid, qr_state;
    protected byte[] getWeiXinQrCode(ZSONObject loginObj) throws Exception{
        qr_state = loginObj.getString("state");

        Connection.Response img = Jsoup.connect("https://open.weixin.qq.com/connect/qrconnect?appid="+loginObj.getString("appid")
                        +"&scope="+loginObj.getString("scope")+"&redirect_uri="+loginObj.getString("redirect_uri")+"&state="+qr_state+"&login_type=jssdk&self_redirect=false&styletype=&sizetype=&bgcolor=&rst=&href="+loginObj.getString("href"))
                .headers(defaultHeader).cookies(cookies).ignoreContentType(true).execute();
//            String skfrmwrespcookie = img.header("skfrmwrespcookie");
        Document p = Jsoup.parse(img.body());
        String src = p.getElementsByTag("img").first().attr("src");
        System.out.println(src);
        qr_uuid = src.substring(src.lastIndexOf("/") + 1);
        return Jsoup.connect("https://open.weixin.qq.com" + src).headers(defaultHeader).cookies(cookies).ignoreContentType(true)
                .execute().bodyAsBytes();
    }

    protected String WeiXinQrLogin() throws Exception {
        Connection.Response Login = Jsoup.connect("https://lp.open.weixin.qq.com/connect/l/qrconnect?uuid="+qr_uuid+"&_="+System.currentTimeMillis())
                .headers(defaultHeader).cookies(cookies).timeout(30000).execute();
        System.out.println(Login.body());
        String body = Login.body();
        if(body.contains("405")) {
            int idx = body.indexOf("'") + 1;
            String code = body.substring(idx, body.indexOf("'", idx));
            return code;
        }else return null;
    }
}
